﻿using System;
using System.Net;
using System.Windows;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Windows.Media.Imaging;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Collections.Generic;

namespace Behaviors
{
    public class DragUIElementImageBehavior : Behavior<UIElement>
    {

        [Description("How transparent should the image that is being dragged appear?")]
        [Category("Appearence")]
        public double Opacity
        {
            get { return (double)GetValue(OpacityProperty); }
            set { SetValue(OpacityProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Opacity.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OpacityProperty =
            DependencyProperty.Register("Opacity", typeof(double), typeof(DragUIElementImageBehavior), new PropertyMetadata(0.5));


        /// <summary>
        /// We drag/drop a copy of the control we are associated with, and when we drop it, the original doesn't move.
        /// </summary>
        protected override void OnAttached()
        {
            AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonDown);
            AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
            AssociatedObject.MouseMove += new MouseEventHandler(AssociatedObject_MouseMove);
        }

        /// <summary>
        /// True when a drag is in progress.
        /// </summary>
        private bool _inDrag = false;

        /// <summary>
        /// The thing we are actually dragging around
        /// </summary>
        private Popup _popup = null;

        /// <summary>
        /// Tracks the images as we move along...
        /// </summary>
        //private DragDecorator _decorator = null;

        /// <summary>
        /// Keep track of the initial spot we put this up!
        /// </summary>
        private Point _initialSpot;

        /// <summary>
        /// The top level - for "hit" testing.
        /// </summary>
        private UIElement _topLevel;

        /// <summary>
        /// Drag is starting. Capture the mouse and setup the UI for the trag.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ///
            /// Generate the UW that we will be dragging around. To get the offset right we have some
            /// fancy footwork we need to do.
            /// 

            Image img = new Image() { Source = new WriteableBitmap(AssociatedObject, new TranslateTransform()) };
            img.Opacity = Opacity;

            if (_popup == null)
            {
                _popup = new Popup();
                _popup.Visibility = Visibility.Visible;
                //_decorator = new DragDecorator();
            }

            _popup.Child = img;
            Point globalPos = e.GetPosition(null);
            _initialSpot = e.GetPosition(AssociatedObject);
            _topLevel = TopLevelItem(AssociatedObject);

            MovePopupToPosition(globalPos);

            _popup.IsOpen = true;

            ///
            /// Get the mouse and setup for tracking in the rest of the control
            /// 

            _inDrag = true;
            AssociatedObject.CaptureMouse();
        }

        /// <summary>
        /// Finds the parent that we can use...
        /// </summary>
        /// <param name="ours"></param>
        /// <returns></returns>
        private UIElement TopLevelItem(UIElement ours)
        {
            DependencyObject current = ours as DependencyObject;
            Func<object> nextGuy = () =>
                {
                    if (current == null)
                    {
                        return current;
                    }
                    var old = current;
                    current = VisualTreeHelper.GetParent((UIElement)old);
                    return old;
                };
            var results = from v in Enumerable.Range(1, 20)
                          let o = nextGuy()
                          select o;
            return results.LastOrDefault(o => o != null) as UIElement;
        }

        /// <summary>
        /// Moves the drag/drop pop-up to the proper spot, keeping track fo the mouse
        /// offset so it remains righ trelative to the mouse pointer.
        /// </summary>
        /// <param name="globalPos"></param>
        private void MovePopupToPosition(Point globalPos)
        {
            _popup.HorizontalOffset = globalPos.X - _initialSpot.X;
            _popup.VerticalOffset = globalPos.Y - _initialSpot.Y;
        }

        /// <summary>
        /// See if we can't get item to move here
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (_inDrag)
            {
                MovePopupToPosition(e.GetPosition(null));

                ///
                /// Are we over something that can deal with this?
                /// 

                var goodElements = GetGoodDragTargetsAtThisLocatoin(e);
                ///
                /// Some sort of feed back I guess...
                /// 

            }
        }

        /// <summary>
        /// Gets a list of good drag targets.
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        private IEnumerable<DragTargetBehavior> GetGoodDragTargetsAtThisLocatoin(MouseEventArgs e)
        {
            var goodElements = from ui in VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(_topLevel), _topLevel)
                               where ui != AssociatedObject && ui != _topLevel
                               from dt in GoodDragTargets(ui)
                               select dt;

            return goodElements;

        }

        /// <summary>
        /// Is this UI element a good drag target?
        /// </summary>
        /// <param name="ui"></param>
        /// <returns></returns>
        private IEnumerable<DragTargetBehavior> GoodDragTargets(UIElement ui)
        {
            var behaviors = ui.GetValue(Interaction.BehaviorsProperty) as ICollection<Behavior>;
            if (behaviors == null)
            {
                return Enumerable.Empty<DragTargetBehavior>();
            }
            var allTests = from b in behaviors
                           let dtarget = b as DragTargetBehavior
                           where dtarget != null && dtarget.DropOK(AssociatedObject)
                           select dtarget;
            return allTests;
        }

        /// <summary>
        /// Clean up now that drag is done.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (_inDrag)
            {
                ///
                /// Do the drop
                /// 

                var goodElement = GetGoodDragTargetsAtThisLocatoin(e).FirstOrDefault();
                if (goodElement != null)
                {
                    goodElement.OnDropObject(AssociatedObject);
                }

                ///
                /// Reset everything!
                /// 

                _inDrag = false;
                _popup.IsOpen = false;
                _popup = null;
                //_decorator = null;
                AssociatedObject.ReleaseMouseCapture();
            }
        }

        /// <summary>
        /// Disconnect ourselves
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
        }
    }
}
